﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Robin.LightNing.Core
{
    /// <summary>
    /// 
    /// </summary>
    public interface IVisitable
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        rbVisitableNode[] GetGraph(object state);
    }

    /// <summary>
    /// 被访问节点是否是一个实体或集合
    /// </summary>
    public enum rbVisitableNodeType
    {
        /// <summary>
        /// 未定义的
        /// </summary>
        Unassigned = 0,
        /// <summary>
        /// 被访问的对象是一个集合
        /// </summary>
        Collection = 1,
        /// <summary>
        /// 被访问的对象是一个实体
        /// </summary>
        Entity = 2
    }

    /// <summary>
    /// 包含正在被访问的节点的信息
    /// </summary>
    public sealed class rbVisitableNode
    {
        internal rbVisitableNode()
        {

        }

        internal rbVisitableNode(object o)
        {
            Obj = o;
        }

        /// <summary>
        /// 节点类型，实体或集合
        /// </summary>
        public rbVisitableNodeType NodeType
        {
            get { return nodeType; }
        }

        /// <summary>
        /// 如果NodeType是rbVisitableNodeType.Collection，该属性有效
        /// </summary>
        public rbEntityCollectionBase Collection
        {
            get
            {
                return (rbEntityCollectionBase)obj;
            }
        }

        /// <summary>
        /// 如果NodeType是rbVisitableNodeType.Entity，该属性有效
        /// </summary>
        public rbEntity Entity
        {
            get
            {
                return (rbEntity)obj;
            }
        }

        /// <summary>
        /// 属性名
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 访问的时候为节点添加User State
        /// </summary>
        public object UserState { get; set; }

        /// <summary>
        /// PruneGraph()方法使用.
        /// </summary>
        /// <param name="parent">父节点</param>
        public void SetValueToNull(object parent)
        {
            if (parent != null)
            {
                fieldInfo.SetValue(parent, null);
            }
        } 

        internal object Obj
        {
            get { return obj; }
            set
            {
                obj = value;
                if (obj == null)
                {
                    nodeType = rbVisitableNodeType.Unassigned;
                }
                else
                {
                    nodeType = (obj is rbEntity) ? rbVisitableNodeType.Entity : rbVisitableNodeType.Collection;
                }
            }
        }


        private object obj;
        private rbVisitableNodeType nodeType;
        internal FieldInfo fieldInfo;
    }

    /// <summary>
    /// 处理访问的时候，传递给每个回调方法. 包含实现逻辑所需要的一切.
    /// </summary>
    public sealed class rbVisitParameters
    {
        /// <summary>
        /// 传递到Visit()方法的根节点, 可以是一个集合或实体.
        /// </summary>
        public object Root;
        /// <summary>
        /// 被访问节点的父节点, 可以是一个集合或实体.
        /// </summary>
        public rbVisitableNode Parent;
        /// <summary>
        /// 正在被访问的节点, 可以是一个集合或实体.
        /// </summary>
        public rbVisitableNode Node;
        /// <summary>
        /// 可以存储数据，贯穿整个访问过程.
        /// </summary>
        public object UserState;
        /// <summary>
        /// 设置为true,当前节点的孩子将无法访问.
        /// </summary>
        public bool ProcessChildren;
    }

    /// <summary>
    /// 用于访问层次结构对象图.不会造成数据的延迟加载.
    /// 主要在RobinLightNing实现的方法如：IsGraphDirty, AcceptChangesGraph等等中内部调用.
    /// </summary>
    public sealed class rbVisitor
    {
        /// <summary>
        /// rbVisitor回调方法签名.如果返回false，visitor将停止.
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public delegate bool VisitCallback(rbVisitParameters parameters);

        private VisitCallback enterCallback = null;
        private VisitCallback exitCallback = null;
        private IList<Object> references = new List<Object>();

        #region Constructors

        private rbVisitor() : this(null) { }

        private rbVisitor(VisitCallback callback)
        {
            enterCallback = callback;
        }

        private rbVisitor(VisitCallback enterCallback, VisitCallback exitCallback)
        {
            this.enterCallback = enterCallback;
            this.exitCallback = exitCallback;
        }

        /// <summary>
        /// 该方法将对每个对象访问一次
        /// </summary>
        /// <param name="root">集合或实体</param>
        /// <param name="callback">回调方法</param>
        /// <returns></returns>
        public static bool Visit(object root, VisitCallback callback)
        {
            return new rbVisitor(callback).Visit(root, (object)null);
        }

        /// <summary>
        /// 以sandwich模式访问对象, EnterCallback, operate on children, ExitCallback.
        /// </summary>
        /// <param name="root">集合或实体</param>
        /// <param name="enterCallback">输入的回调函数</param>
        /// <param name="exitCallback">结束的回调函数</param>
        /// <returns></returns>
        public static bool Visit(object root, VisitCallback enterCallback, VisitCallback exitCallback)
        {
            return new rbVisitor(enterCallback, exitCallback).Visit(root, (object)null);
        }

        /// <summary>
        /// 该方法将对每个对象访问一次
        /// </summary>
        /// <param name="root">集合或实体</param>
        /// <param name="callback">回调函数</param>
        /// <param name="userState">传递任何你喜欢的东西，提供给回调函数</param>
        /// <returns></returns>
        public static bool Visit(object root, VisitCallback callback, object userState)
        {
            return new rbVisitor(callback).Visit(root, userState);
        }

        /// <summary>
        /// 以sandwich模式访问对象, EnterCallback, operate on children, ExitCallback.
        /// </summary>
        /// <param name="root">集合或实体</param>
        /// <param name="enterCallback">输入的回调函数</param>
        /// <param name="exitCallback">结束的回调函数</param>
        /// <param name="userState">传递任何你喜欢的东西，提供给回调函数</param>
        /// <returns></returns>
        public static bool Visit(object root, VisitCallback enterCallback, VisitCallback exitCallback, object userState)
        {
            return new rbVisitor(enterCallback, exitCallback).Visit(root, userState);
        }

        #endregion

        #region Methods

        private bool AddIfNewReference(rbVisitableNode node)
        {
            for (int i = 0; i < references.Count; i++)
            {
                if (Object.ReferenceEquals(node.Obj, references[i]))
                {
                    // existing reference
                    return false;
                }
            }

            // add at beginning of list assuming that more recent items will collide first
            references.Insert(0, node.Obj);
            return true;
        }

        private bool VisitNode(rbVisitParameters p)
        {
            bool keepGoing = true;

            if (p.Node != null && AddIfNewReference(p.Node))
            {
                if (!enterCallback(p))
                {
                    keepGoing = false;
                }

                if (p.ProcessChildren && keepGoing && !VisitNodeReferences(p))
                {
                    keepGoing = false;
                }

                if (exitCallback != null && !exitCallback(p))
                {
                    keepGoing = false;
                }
            }

            return keepGoing;
        }

        private bool VisitNodeReferences(rbVisitParameters p)
        {
            Type type = p.Node.Obj.GetType();

            rbVisitableNode parent = p.Parent;
            rbVisitableNode node = p.Node;

            p.Node = new rbVisitableNode();
            p.Parent = new rbVisitableNode();

            try
            {
                if (!typeof(String).IsAssignableFrom(type) && typeof(IEnumerable).IsAssignableFrom(type))
                {
                    p.Parent = node;

                    int i = 0;
                    foreach (object item in (IEnumerable)node.Obj)
                    {
                        p.Node.Obj = item;
                        p.Node.PropertyName = "";
                        p.ProcessChildren = true;

                        if (!VisitNode(p))
                        {
                            return false;
                        }
                        i++;
                    }
                }

                if (typeof(IVisitable).IsAssignableFrom(type))
                {
                    rbVisitableNode[] nodes = ((IVisitable)node.Obj).GetGraph(enterCallback);
                    if (nodes != null)
                    {
                        p.Parent = node;

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            p.Node = nodes[i];

                            p.ProcessChildren = true;
                            if (!VisitNode(p))
                            {
                                return false;
                            }
                        }
                    }
                }
            }
            finally
            {
                p.Parent = parent;
                p.Node = node;
            }

            return true;
        }

        private bool Visit(object root, object userState)
        {
            return Visit(root, userState, true);
        }

        private bool Visit(object root, object userState, bool clearReferences)
        {
            if (clearReferences)
            {
                references.Clear();
            }

            rbVisitParameters parameters = new rbVisitParameters()
            {
                Root = root,
                ProcessChildren = true,
                Node = new rbVisitableNode(root),
                Parent = new rbVisitableNode(),
                UserState = userState
            };

            return VisitNode(parameters);
        }

        #endregion
    }
}
